/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"
#include <algorithm>

/////////////////////////////////////////////////////////////////////////////

MonitorPool *monitors = NULL;

BOOL CALLBACK enumMonitors(HMONITOR monitor, HDC hdc, LPRECT boundingRect, LPARAM data)
{
	vector<Monitor*> *monitorList = (vector<Monitor*>*)data;
	string monitorName = retprintf("%i", monitorList->size()+1);
	Monitor *newMonitor = new Monitor(monitor, monitorName, Rect(*boundingRect));
	monitorList->push_back(newMonitor);
	return TRUE;
}

MonitorPool::MonitorPool()
{
	EnumDisplayMonitors(NULL, NULL, enumMonitors, (LPARAM)&monitors);
}

MonitorPool::~MonitorPool()
{
	for(unsigned ii=0; ii<monitors.size(); ii++)
		delete monitors[ii];
	monitors.clear();
}

void MonitorPool::refresh()
{
	vector<Monitor*> oldMonitors;
	oldMonitors = monitors;
	vector<Monitor*> newMonitors;
	EnumDisplayMonitors(NULL, NULL, enumMonitors, (LPARAM)&newMonitors);
	
	// Match new monitors to old monitors.
	for(vector<Monitor*>::iterator ii=newMonitors.begin(); ii!=newMonitors.end(); ii++)
		(*ii)->visited = false;
	
	// First, look for exact matches (by bounding rect) only.
	for(vector<Monitor*>::iterator ii=newMonitors.begin(); ii!=newMonitors.end(); ii++)
	{
		for(vector<Monitor*>::iterator jj=oldMonitors.begin(); jj!=oldMonitors.end(); jj++)
		{
			if((*jj)->getBoundingRect() == (*ii)->getBoundingRect())
			{
				//trace << "Exact match on monitor "<<(*ii)->getName()<<"\n"; //DEBUG
				(*jj)->updateFrom(*ii);
				(*jj)->visited = true;
				
				delete (*ii);
				(*ii) = (*jj);
				
				oldMonitors.erase(jj);
				break;
			}
		}
	}
	
	// Look for new monitors that overlap the coordinate space of an old
	// monitor, match them up, and adjust window positions on that monitor.
	for(vector<Monitor*>::iterator ii=newMonitors.begin(); ii!=newMonitors.end(); ii++)
	{
		if((*ii)->visited)
			continue;
		
		for(vector<Monitor*>::iterator jj=oldMonitors.begin(); jj!=oldMonitors.end(); jj++)
		{
			if((*jj)->getBoundingRect().overlaps((*ii)->getBoundingRect()))
			{
				//trace << "Fuzzy match on monitor "<<(*ii)->getName()<<"\n"; //DEBUG
				// TODO: Handle the change in size
				
				(*jj)->updateFrom(*ii);
				(*jj)->visited = true;
				
				delete (*ii);
				(*ii) = (*jj);
				
				oldMonitors.erase(jj);
				break;
			}
		}
	}
	
	// Hide (move off-screen) the desk shown on any deleted monitors that
	// were not matched
	vwm->beginMovingWindows();
	
	for(vector<Monitor*>::iterator ii=oldMonitors.begin(); ii!=oldMonitors.end(); ii++)
	{
		Monitor *monitor = *ii;
		//trace << "Dropped monitor "<<monitor->getName()<<"\n"; //DEBUG
		VirtualDesktop *desk = monitor->getDesk();
		desk->hide();
		delete monitor;
	}
	
	// Commit the change in monitor list
	this->monitors = newMonitors;
	
	// If there are new monitors and more total monitors than desktops, add
	// empty desktops
	while((int)this->monitors.size() > vwm->numDesktops())
	{
		vwm->createDesktop(vwm->numDesktops());
	}
	
	// Pick a desk to show on any new monitors
	for(vector<Monitor*>::iterator ii=newMonitors.begin(); ii!=newMonitors.end(); ii++)
	{
		Monitor *monitor = *ii;
		if(!monitor->getDesk())
		{
			VirtualDesktop *desk = NULL;
			for(int jj=0; jj<vwm->numDesktops(); jj++) {
				if(!vwm->getDesk(jj)->isVisible()) {
					desk = vwm->getDesk(jj);
					break;
				}
			}
			if(desk)
				desk->show(monitor);
		}
	}
	
	vwm->finishMovingWindows();
}

int MonitorPool::numMonitors()
{
	return monitors.size();
}

vector<Monitor*> MonitorPool::findMonitor(Rect windowPos)
{
	// Given the location of a window, find all the monitors that window
	// is (partially or completely) visible on. If the window is off-screen
	// (eg, on a virtual desktop that's not selected), returns the empty set.
	// If the window is on more than one monitor, sorted in descending order
	// by visible area.
	vector<Monitor*> ret;
	
	for(unsigned ii=0; ii<monitors.size(); ii++)
	{
		if(monitors[ii]->getBoundingRect().overlaps(windowPos))
			ret.push_back(monitors[ii]);
	}
	
	struct MonitorComparator
	{
		MonitorComparator(Rect windowPos)
			:windowPos(windowPos) {}
		bool operator()(Monitor *a, Monitor *b)
			{ return a->getOverlapArea(windowPos) > b->getOverlapArea(windowPos); }
		Rect windowPos;
	};
	MonitorComparator comparator(windowPos);
	
	std::sort(ret.begin(), ret.end(), comparator);
	
	return ret;
}

Monitor *MonitorPool::findMonitor(Point cursorPos)
{
	for(unsigned ii=0; ii<monitors.size(); ii++)
		if(monitors[ii]->getBoundingRect().containsPoint(cursorPos.x, cursorPos.y))
			return monitors[ii];
	return NULL;
}

Monitor *MonitorPool::getMonitor(int index)
{
	if(index<0 || index>=(int)monitors.size())
		return NULL;
	return monitors[index];
}

Monitor *MonitorPool::getPrimaryMonitor()
{
	return monitors[0];
}

/////////////////////////////////////////////////////////////////////////////

Monitor::Monitor(HMONITOR handle, string name, Rect boundingRect)
{
	this->handle = handle;
	this->boundingRect = boundingRect;
	currentDesk = NULL;
	this->name = name;
}

Monitor::~Monitor()
{
}

void Monitor::updateFrom(Monitor *newMonitor)
{
	handle = newMonitor->handle;
	name = newMonitor->name;
	boundingRect = newMonitor->boundingRect;
}

VirtualDesktop *Monitor::getDesk()
	{ return currentDesk; }
void Monitor::setDesk(VirtualDesktop *desk)
	{ currentDesk = desk; }
Rect Monitor::getBoundingRect() const
	{ return boundingRect; }
string Monitor::getName()
	{ return name; }

vector<ScreenbarPanel*> Monitor::getPanelsOnMonitor()
{
	vector<ScreenbarPanel*> allPanels;
	getPanels(&allPanels);
	
	vector<ScreenbarPanel*> ret;
	for(unsigned ii=0; ii<allPanels.size(); ii++) {
		if(allPanels[ii]->getMonitor() == this)
			ret.push_back(allPanels[ii]);
	}
	return ret;
}

int Monitor::getOverlapArea(Rect &rect) const
{
	if(!rect.overlaps(boundingRect))
		return 0;
	
	Rect intersection = rect.intersect(boundingRect);
	return intersection.width*intersection.height;
}

Rect Monitor::getMaximizeArea() const
{
	MONITORINFO monitorInfo;
	monitorInfo.cbSize = sizeof monitorInfo;
	GetMonitorInfo(handle, &monitorInfo);
	
	return monitorInfo.rcWork;
}
